Make substrate's namespace, Service names and ServiceAccount names configurable - #31
Open
jjamroga wants to merge 12 commits into
Open
Make substrate's namespace, Service names and ServiceAccount names configurable#31jjamroga wants to merge 12 commits into
jjamroga wants to merge 12 commits into
Conversation
Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io>
Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io>
Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io>
Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io>
The chart already renders every resource into the release namespace and
passes the resolved Service names to atenet-dns, but several identity
checks still assume substrate occupies "ate-system". Each one fails
closed in a relocated install, and none of them fails in a way that
points at the namespace:
- atunnel verifies atelet's SPIFFE ID on the credential broker
connection against a hardcoded ID, so no actor can obtain a
certificate. atunnel runs in the actor's pod, whose POD_NAMESPACE is
the actor's and not atelet's, so the expected identity is passed in.
- The NetworkPolicy the WorkerPool controller generates admits ingress
only from "ate-system". Pointed at the wrong namespace it admits
nobody, and the CNI drops every request to the pool's actors with no
error from substrate itself.
- actoridentity rejects atelet's MintCert/MintJWT calls as "not
atelet". It re-declared the atelet SPIFFE segments to avoid depending
on controlapi, with a comment asking for a shared package once a
third consumer appeared; atunnel is that third consumer, so the
segments move to installdefaults.
- The egress MITM CA pool Secret was looked up in a hardcoded
namespace.
atunnel already took the atenet-router identity as a flag, so the atelet
identity follows that convention. Both flags previously relied on their
defaults being right; ate-controller now passes them explicitly, since it
is the component that knows which namespace the control plane occupies.
Without that the new flag would be inert — correct only where the old
hardcoded value was already correct.
hack/gen-rbac.sh now templatizes the namespace of the generated
ate-controller Role alongside its name. A kubebuilder rbac marker has to
name a literal namespace, so controller-gen emits "ate-system"; left
alone, the Role lands outside the release namespace and ate-controller
loses its endpointslices permission. manifests/ate-install/ is unchanged:
it renders with --namespace ate-system, so the templated value produces
the same bytes.
Renaming the release is a separate axis and still unsupported: the chart
prefixes ServiceAccount names, which SPIFFE IDs embed, and those names
remain constants in installdefaults.
The chart installs into whatever namespace the release targets, but the tooling around it did not: hack/install-ate.sh hardcoded "ate-system" in 52 places and internal/e2e in 9, so a relocated install could not be bootstrapped or exercised. The mTLS authorities landed in ate-system while the control plane looked for them in the release namespace, and the e2e suites addressed the router and egress CA pool in a namespace they were no longer in. install-ate.sh now reads ATE_NAMESPACE and the e2e harness reads E2E_SYSTEM_NAMESPACE, both defaulting to ate-system so existing flows and CI are unaffected. The one part that is not a substitution is namespace creation. manifests/ate-install/ate-system-namespace.yaml names ate-system and carries labels the chart also sets, so it stays the source of truth for the canonical install; ensure_ate_namespace falls back to a plain create for any other namespace.
Two gaps that only appear once substrate is installed outside ate-system, both found by running the helm-e2e flow against a relocated release. ate-controller reads the namespace from the downward API to build the atelet and atenet-router SPIFFE identities it hands to each worker's atunnel, but its template projected no POD_NAMESPACE. It fell back to the compiled-in default and stamped every worker with spiffe://cluster.local/ns/ate-system/sa/atelet, so atunnel rejected the credential broker with "credential broker is not atelet" and no actor could obtain a certificate. Five other templates already project the variable; ate-controller was the only consumer that did not, and the rendered manifest under manifests/ate-install/ has carried it all along. atelet's credential broker dials ateapi to mint those certificates. The chart passes a resolved --ateapi-address to ate-controller, atenet-router and atenet-egress, but not to atelet, which fell back to k8s:///api.ate-system.svc:443 and failed with "k8sresolver: watch error for ate-system/api ... endpointslices is forbidden" — the RBAC it has covers the release namespace, not the one it was dialing. --ateapi-server-name has the same default and the same problem, since it is the name checked on ateapi's serving cert.
The probe hardcoded atenet-egress.ate-system.svc:443 as its --gateway-address default and nothing overrode it, so against a relocated install every egressauthz case failed in the tunnel stage with "no such host" — before reaching the ext_proc identity check the tests exist to cover, which reported as an authorization failure rather than a lookup one. The probe runs inside the sandbox and cannot infer substrate's namespace, so the suite substitutes it into the manifest the same way it already substitutes the probe's own namespace.
Two pieces of the upstream branch this fork's version had not picked up. The atelet dialer resolved the namespace from POD_NAMESPACE at the point of use, independently of the informer that was already given one. Both read the same variable so the behaviour matches, but the namespace is now threaded in alongside the informer's, which keeps a single resolution point and matches upstream so the eventual rebase is a no-op here. The trust-domain and service-account segments move to installdefaults with it. atenet-router's /statusz looked up its own ClusterIP under the hardcoded Service name atenet-router, which is wrong for any release this chart prefixes. It takes --router-service-name, and the chart passes the name it actually rendered.
A SPIFFE ID names a ServiceAccount as well as a namespace, and the ServiceAccount name was still a constant. This chart prefixes every resource name for a release not called "substrate" — which is every install that consumes substrate as a subchart, since a subchart inherits the parent's release name — so ateapi and each worker's atunnel expected an account that does not exist and rejected the peer that does. ateapi takes --atelet-service-account and ate-controller takes that plus --router-service-account, both defaulting to the canonical names, and the chart passes the names it actually rendered. ateapi's dialer and actoridentity now take the whole expected identity rather than a namespace they rebuild it from. Both only ever used the namespace to construct that one string, and having a single place decide what atelet's identity is beats two places agreeing on how to spell it.
Installing substrate as a subchart renames more than the ServiceAccounts. The Service fronting ateapi becomes <release>-api and the ServiceAccount the CLI mints its token from becomes <release>-ate-client, and both were constants: kubectl-ate port-forwarded to a Service that does not exist, and once that was fixed it minted a token for the wrong audience and ateapi rejected it as an invalid bearer token. The client reads ATE_API_SERVICE_NAME and ATE_CLIENT_SERVICE_ACCOUNT alongside the ATE_NAMESPACE it already read; it runs outside the cluster and cannot discover any of this. hack/install-ate.sh reads the same ATE_API_SERVICE_NAME, since the audience it writes into the API authentication config has to name the same Service the client dials. All three default to the canonical names.
The harness looked up ate-controller, ate-api-server, atenet-router and the egress CA pool Secret by their canonical names. A release the chart prefixes — which is any subchart install, since a subchart inherits the parent's release name — renders them as <release>-ate-controller and so on, and every suite failed in preflight before running a single case. E2E_RESOURCE_PREFIX carries what the install under test prepends, empty by default.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Substrate assumed it was installed as the canonical release: namespace
ate-system, Services namedapi/atenet-router/dns, andServiceAccounts named
atelet/atenet-router. All of those werecompiled-in constants, so an install anywhere else failed — and mostly failed
closed, in ways that do not point at naming.
This makes the namespace, the Service names and the ServiceAccount names
configuration, and wires the chart to pass what it actually rendered.
Everything defaults to the canonical values, so an existing install is
unaffected.
The Go and script changes here are the same ones proposed upstream in
agent-substrate#350. The chart changes are fork-only, since the chart
lives here. Once the upstream PR lands, this branch rebases onto it and only
the chart commits remain.
Why this matters here
The chart prefixes every resource name for a release not called
substrate.That is exactly what consuming substrate as a subchart produces, since a
subchart inherits the parent's release name — so the ServiceAccounts render as
<release>-ateletand<release>-atenet-router. A SPIFFE ID names aServiceAccount as well as a namespace, so identities built from the constants
named accounts that did not exist and every peer was rejected.
Concretely, before this change a worker in a subchart install was told to
expect
spiffe://cluster.local/ns/ate-system/sa/ateletwhile atelet actuallypresented
spiffe://cluster.local/ns/<release-ns>/sa/<release>-atelet. Noactor could obtain a credential.
What was broken
credential broker is not atelet; no actor got a certificatePOD_NAMESPACE--ateapi-addressendpointslices is forbiddenservices "api" not found, theninvalid bearer tokenNotes for review
hack/gen-rbac.shtemplatizes the Role's namespace. A kubebuilder rbacmarker has to name a literal namespace, so controller-gen emits the canonical
one; the script already rewrote the Role's name and now rewrites its
namespace the same way.
manifests/ate-install/is unchanged, because itrenders with
--namespace ate-systemand the templated value produces thesame bytes.
POD_NAMESPACEconsumer whose template did notproject it. Five other templates already did, and the rendered manifest has
carried it all along, so the chart had drifted from its own output.
cluster, so it has no downward API and nothing to discover from.
or renamed install cannot be bootstrapped or exercised without them.
Testing
Verified on kind in three topologies.
Relocated namespace — installed into a namespace other than
ate-system:control plane healthy,
kubectl-ateauthenticating, actor template reachingReady, substrate e2e suites passing.Renamed release — release name other than
substrate, so every resource isprefixed: same results, with the identities correct on both the namespace and
the ServiceAccount.
As a subchart of kagent —
helm install kagent helm/kagent --set substrate.enabled=true, substrate pulled as an OCI dependency. Everything cameup in one namespace under prefixed names, kagent's WorkerPool was reconciled by
substrate's controller, and the resulting worker carried
kagent's own e2e suite passed against that install, covering agent interaction,
resume, persistence and checkpointing. The one failure was a foreign-key
violation in kagent's own database, in a path substrate is not part of.
Unit tests cover the relocated and renamed cases specifically. The pre-existing
tests all passed canonical values, so they would have held against the
hardcoding they are meant to catch; the new ones were checked by reintroducing
the hardcoding and confirming they fail.
Follow-up
kagent needs one change of its own to consume this as a subchart: its
controller.substrate.ateApiEndpointdefaults toapi.ate-system.svc, which isright for installing substrate as a separate release but wrong when the Service
is
<release>-api. This chart already exposessubstrate.ateApi.endpointandsubstrate.atenetRouter.urlfor parent charts to use.Worth deciding separately: whether kagent should ship with the substrate
subchart enabled. It is gated in two charts today —
kagent-crdsandkagent,both defaulting to false — so enabling only one installs no CRDs and silently
does nothing.